package winOrDie.Net.P2P;


/*******************************************************************************

"FreePastry" Peer-to-Peer Application Development Substrate

Copyright 2002-2007, Rice University. Copyright 2006-2007, Max Planck Institute 
for Software Systems.  All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:

- Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.

- Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.

- Neither the name of Rice  University (RICE), Max Planck Institute for Software 
Systems (MPI-SWS) nor the names of its contributors may be used to endorse or 
promote products derived from this software without specific prior written 
permission.

This software is provided by RICE, MPI-SWS and the contributors on an "as is" 
basis, without any representations or warranties of any kind, express or implied 
including, but not limited to, representations or warranties of 
non-infringement, merchantability or fitness for a particular purpose. In no 
event shall RICE, MPI-SWS or contributors be liable for any direct, indirect, 
incidental, special, exemplary, or consequential damages (including, but not 
limited to, procurement of substitute goods or services; loss of use, data, or 
profits; or business interruption) however caused and on any theory of 
liability, whether in contract, strict liability, or tort (including negligence
or otherwise) arising in any way out of the use of this software, even if 
advised of the possibility of such damage.

 *******************************************************************************/



import java.util.Hashtable;
import java.util.Iterator;
import java.util.Vector;

import rice.p2p.commonapi.Application;
import rice.p2p.commonapi.CancellableTask;
import rice.p2p.commonapi.Endpoint;
import rice.p2p.commonapi.Id;
import rice.p2p.commonapi.Message;
import rice.p2p.commonapi.Node;
import rice.p2p.commonapi.NodeHandle;
import rice.p2p.commonapi.RouteMessage;
import rice.p2p.scribe.Scribe;
import rice.p2p.scribe.ScribeClient;
import rice.p2p.scribe.ScribeContent;
import rice.p2p.scribe.ScribeImpl;
import rice.p2p.scribe.Topic;
import rice.pastry.commonapi.PastryIdFactory;


/**
 * This public class provides {@link WoD_Scribe_Channel} 
 * 
 * It's a FreePastry application that uses Scribe service.
 *
 */
public class WoD_Scribe_Channel implements ScribeClient, Application {

	private Vector<WoD_Scribe_Channel_Listener> listeners = null;
	
	public synchronized void add_WoD_Scribe_Channel_Listener(WoD_Scribe_Channel_Listener listener)
	{
		listeners.add(listener);
	}
	
	public synchronized void remove_WoD_Scribe_Channel_Listener(WoD_Scribe_Channel_Listener listener)
	{
		listeners.remove(listener);
	}
	
	protected void fire_WoD_Scribe_Channel_Event(WoD_Scribe_Channel_Event event)
	{
		
//		Iterator<WoD_Scribe_Channel_Listener> iterator = listeners.iterator();
//		synchronized (iterator) {
//			while (iterator.hasNext()) {
//				iterator.next().on_WoD_Scribe_Channel_Change(event);
//				
//			}
//		}
		

		for (WoD_Scribe_Channel_Listener listener : listeners) {
			listener.on_WoD_Scribe_Channel_Change(event);
		}

	}
	
	private WoD_ScribeContent content = null;

	private Hashtable<Id, String> hashtable_Peers = new Hashtable<Id, String>();
	
	/**
	 * The message sequence number.  Will be incremented after each send.
	 */
	int seqNum = 0;

	/**
	 * This task kicks off publishing and anycasting.
	 * We hold it around in case we ever want to cancel the publishTask.
	 */
	CancellableTask publishTask = null;


	/** 
	 * My handle to a scribe impl.
	 */
	Scribe myScribe;

	/**
	 * The only topic this appl is subscribing to.
	 */
	Topic myTopic;


	
	/**
	 * If this node is root this attribute is set to true
	 */
	private boolean ImRoot = false;
	private boolean check_Root_Loop = false;
	
	
	/**
	 * The {@link NodeHandle} for scribe topic root
	 */
	private NodeHandle nodeHandle_Root = null;
	
	/**
	 * The Endpoint represents the underlieing node.  By making calls on the 
	 * Endpoint, it assures that the message will be delivered to a MyApp on whichever
	 * node the message is intended for.
	 */
	protected Endpoint endpoint;

	/**
	 * This public method returns full String of this node ID
	 * @return String
	 */
	public Endpoint getEndpoint()
	{
		return endpoint;
	}
	
	public NodeHandle getNodeHandle_Root() {
		return nodeHandle_Root;
	}
	
	class PublishContent implements Message {
		public int getPriority() {
			return MAX_PRIORITY;
		}

	}


	/**
	 * Construct {@link WoD_Scribe_Channel} application from Node
	 * @param node
	 */
	public WoD_Scribe_Channel(Node node) 
	{
		this.endpoint = node.buildEndpoint(this, "myinstance");

		// construct Scribe
		myScribe = new ScribeImpl(node,"myScribeInstance");

		// construct the topic
		myTopic = new Topic(new PastryIdFactory(node.getEnvironment()), "WoD Game");

		// initialize listeners
		listeners = new Vector<WoD_Scribe_Channel_Listener>();
		
	}
	
	/**
	 * This public method starts this {@link WoD_Scribe_Channel}. Once channel is started it
	 * began to receive messages.
	 */
	public void start_Wod_Scribe_Channel()
	{
		// now we can receive messages
		endpoint.register();
	}
	
	/**
	 * This private method checks if this node is root node
	 */
	public void check_Root()
	{
		Runnable runnable = new Runnable() {
			
			@Override
			public void run() {
				
				check_Root_Loop = true;
				while (check_Root_Loop) {
					
					/*
					 * If this node change to root Send's multicast message to announce it 
					 */
					if (!ImRoot) {
						ImRoot = isRoot();
						
						/**
						 * If this node became Root do some task. 
						 * 
						 * Send Scribe message type NEW_ROOT and notify 
						 */
						if (ImRoot) {
							
							/*
							 * Send message type NEW_ROOT
							 */
							nodeHandle_Root = endpoint.getLocalNodeHandle();
						
							WoD_ScribeContent woDScribeContent = new WoD_ScribeContent(endpoint.getLocalNodeHandle(), seqNum);
							String message = WoD_ScribeContent.Message_Headers.NEW_ROOT;
							woDScribeContent.setMessageType(WoD_ScribeContent.Message_Types.NEW_ROOT);
							try {
								woDScribeContent.setMessage(message);
							} catch (Exception e) {
								// TODO Auto-generated catch block
								e.printStackTrace();
							}
							myScribe.publish(myTopic, woDScribeContent); 
							seqNum++;
							
							/*
							 * Notify event I_AM_ROOT to listeners
							 */
							WoD_Scribe_Channel_Event event = new WoD_Scribe_Channel_Event(this);
							event.setEvent_Type(WoD_Scribe_Channel_Event.I_AM_ROOT);
							fire_WoD_Scribe_Channel_Event(event);
							
							
							
					    	//startPublishTask();
						}
					}
					
					/*
					 * If this node leave root mode. Cancel publish tasks
					 */
					else
					{
						ImRoot = isRoot();
						if (!ImRoot)
						{
							//publishTask.cancel();	
						}
					}
					
					/*
					 * Wait some time to check Root
					 */
					try {
						Thread.currentThread().sleep(1000);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
						check_Root_Loop = false;
					}
				}
			}
		};
		
		
		Thread thread = new Thread(runnable);
		//thread.setDaemon(true);
		thread.start();
		
	}
	

	/**
	 * Subscribes to myTopic.
	 */
	public void subscribe() {
		myScribe.subscribe(myTopic, this);
	}
	
	/**
	 * Unsubscribes to myTopic
	 */
	public void unsubscribe()
	{
		myScribe.unsubscribe(myTopic, this);
		check_Root_Loop = false;
	}

	/**
	 * Starts the publish task.
	 */
	public void startPublishTask() {
		publishTask = endpoint.scheduleMessage(new PublishContent(), 5000, 5000);    
	}


	/**
	 * Called when we receive an anycast.  If we return
	 * false, it will be delivered elsewhere.  Returning true
	 * stops the message here.
	 */
	public boolean anycast(Topic topic, ScribeContent content) {

		WoD_ScribeContent woDScribeContent = (WoD_ScribeContent)content;
		//String my_NodeID = endpoint.getLocalNodeHandle().getId().toStringFull();
		//String message_DestinationNodeID = woDScribeContent.getDestination().getId().toStringFull();
		

		String my_NodeID = endpoint.getId().toStringFull();
		//byte[] message_DestinationNodeID = woDScribeContent.getDestination_Node_ID();
		String destination_ID = woDScribeContent.getDestination_Node_ID();
		
		/**
		 * Check anycast message destination
		 */
		if (my_NodeID.equals(destination_ID))
		{
			/**
			 * If it's application message type notify event to listeners
			 */
			if (woDScribeContent.getMessageType() == WoD_ScribeContent.Message_Types.APPLICATION)
			{
				WoD_Scribe_Channel_Event event = new WoD_Scribe_Channel_Event(this);
				event.setEvent_Type(WoD_Scribe_Channel_Event.MESSAGE_RECEIVED);
				event.setData(woDScribeContent);
				fire_WoD_Scribe_Channel_Event(event);
			}
			return true;
		}
		

		return false;
	}

	@Override
	public void childAdded(Topic topic, NodeHandle child) {
		// TODO Auto-generated method stub

	}

	@Override
	public void childRemoved(Topic topic, NodeHandle child) {
		// TODO Auto-generated method stub

	}

	/**
	 * This method is invoked when a message is delivered for a topic this client is interested in.
	 * Called whenever we receive a published message.
	 */
	public void deliver(Topic topic, ScribeContent content) {
		
		if (content instanceof WoD_ScribeContent) {
			WoD_ScribeContent woDScribeContent = (WoD_ScribeContent) content;
			WoD_Scribe_Channel_Event event = null;

			switch (woDScribeContent.getMessageType()) {

			/**
			 * Application messages are notified to listeners
			 */
			case WoD_ScribeContent.Message_Types.APPLICATION:
				event = new WoD_Scribe_Channel_Event(this);
				event.setEvent_Type(WoD_Scribe_Channel_Event.MESSAGE_RECEIVED);
				event.setData(woDScribeContent);
				fire_WoD_Scribe_Channel_Event(event);
				break;

			/**
			 * New Root messages changes root Node. Root node not notify
			 */
			case WoD_ScribeContent.Message_Types.NEW_ROOT:
				nodeHandle_Root = woDScribeContent.getFrom();

				if ((!ImRoot) || (!nodeHandle_Root.getId().toStringFull().equals(endpoint.getId().toStringFull())))
				{
					event = new WoD_Scribe_Channel_Event(this);
					event.setEvent_Type(WoD_Scribe_Channel_Event.NEW_ROOT);
					event.setData(woDScribeContent);
					fire_WoD_Scribe_Channel_Event(event);
				}
				break;
				
				
			/**
			 * New Root messages changes root Node. Root node not notify
			 */
			case WoD_ScribeContent.Message_Types.NODE_DOWN:
				event = new WoD_Scribe_Channel_Event(this);
				event.setEvent_Type(WoD_Scribe_Channel_Event.NODE_DOWN);
				event.setData(woDScribeContent);
				fire_WoD_Scribe_Channel_Event(event);
				break;
			}

	

			/**
			 * If this node is the topic root then process more messages
			 */
			if (ImRoot)
			{
				
				switch (woDScribeContent.getMessageType()) {

				case WoD_ScribeContent.Message_Types.NEW_NODE:
					event = new WoD_Scribe_Channel_Event(this);
					event.setEvent_Type(WoD_Scribe_Channel_Event.NEW_NODE);
					event.setData(woDScribeContent);
					fire_WoD_Scribe_Channel_Event(event);
					break;
					



				default:
					break;
				}

			}
		}

		
//		else
//		{
//			System.out.println("MyScribeClient.deliver("+topic+","+content+")");
//		}

//		if (((WoD_ScribeContent)content).from == null) {
//			new Exception("Stack Trace").printStackTrace();
//		}

	}

	@Override
	public void subscribeFailed(Topic topic) {
		// TODO Auto-generated method stub

	}


	/**
	 * This method is called on the application at the destination node for the given id.
	 * Part of the Application interface.  Will receive PublishContent every so often.
	 */
	public void deliver(Id id, Message message) {
		if (message instanceof PublishContent) {
			System.out.println("Public Content received");
		}

	}

	@Override
	public boolean forward(RouteMessage message) {
		// TODO Auto-generated method stub
		return false;
	}

	@Override
	/**
	 * This method is invoked to inform the application that the given node has either joined or 
	 * left the neighbor set of the local node, as the set would be returned by the neighborSet call.
	 * 
	 */
	public void update(NodeHandle handle, boolean joined) {
		
		if (!joined)
		{
			//sendMulticast("Node " + handle.getId() + " has leave the ring.");

			// Prepare message
			
			String message = handle.getId().toStringFull();
			
			// Prepare WoD_ScribeContent
			WoD_ScribeContent woDScribeContent = new WoD_ScribeContent(endpoint.getLocalNodeHandle(), seqNum);
			woDScribeContent.setMessageType(WoD_ScribeContent.Message_Types.NODE_DOWN);
			try {
				woDScribeContent.setMessage(message);
				
				// Publish message
				myScribe.publish(myTopic, woDScribeContent);
				seqNum++;
			} catch (Exception e) {
				e.printStackTrace();
			}
			
		}
		
	}



	/**
	 * Sends multicast message.
	 */
	public void sendMulticast(String message) {
		//System.out.println("Node "+endpoint.getLocalNodeHandle()+" broadcasting "+seqNum+ " " + message.toString());
		WoD_ScribeContent woDScribeContent = new WoD_ScribeContent(endpoint.getLocalNodeHandle(), seqNum);
		try {
			woDScribeContent.setMessage(message);
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		woDScribeContent.setMessageType(WoD_ScribeContent.Message_Types.APPLICATION);
		myScribe.publish(myTopic, woDScribeContent); 
		seqNum++;
	}

	
	/**
	 * Sends multicast message.
	 */
	public void sendMulticast(byte[] message) {
		//System.out.println("Node "+endpoint.getLocalNodeHandle()+" broadcasting "+seqNum+ " " + message.toString());
		WoD_ScribeContent woDScribeContent = new WoD_ScribeContent(endpoint.getLocalNodeHandle(), seqNum);
		woDScribeContent.setMessage(message);
		woDScribeContent.setMessageType(WoD_ScribeContent.Message_Types.APPLICATION);
		myScribe.publish(myTopic, woDScribeContent); 
		seqNum++;
	}

	/**
	 * Send new Node Message
	 */
	public void send_New_Node()
	{
		WoD_ScribeContent woDScribeContent = new WoD_ScribeContent(endpoint.getLocalNodeHandle(), seqNum);
		woDScribeContent.setMessageType(WoD_ScribeContent.Message_Types.NEW_NODE);
		myScribe.publish(myTopic, woDScribeContent); 
		seqNum++;
	}
	
	/**
	 * Sends anycast message.
	 */
	public void sendAnycast(String destination_Node_ID ,String message) {
		//System.out.println("Node "+endpoint.getLocalNodeHandle()+" anycasting "+seqNum);
		WoD_ScribeContent woDScribeContent = new WoD_ScribeContent(endpoint.getLocalNodeHandle(), seqNum);
		woDScribeContent.setDestination_Node_ID(destination_Node_ID);
		try {
			woDScribeContent.setMessage(message);
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		woDScribeContent.setMessageType(WoD_ScribeContent.Message_Types.APPLICATION);
		myScribe.anycast(myTopic, woDScribeContent); 
		seqNum++;
	}	

	/**
	 * Sends anycast message.
	 */
	public void sendAnycast(String destination_Node_ID ,byte[] message) {
		//System.out.println("Node "+endpoint.getLocalNodeHandle()+" anycasting "+seqNum);
		WoD_ScribeContent woDScribeContent = new WoD_ScribeContent(endpoint.getLocalNodeHandle(), seqNum);
		woDScribeContent.setDestination_Node_ID(destination_Node_ID);
		woDScribeContent.setMessage(message);
		woDScribeContent.setMessageType(WoD_ScribeContent.Message_Types.APPLICATION);
		myScribe.anycast(myTopic, woDScribeContent); 
		seqNum++;
	}	

	
	/************ Some passthrough accessors for the myScribe *************/
	public boolean isRoot() {
		return myScribe.isRoot(myTopic);
	}

	public NodeHandle getParent() {
		// NOTE: Was just added to the Scribe interface.  May need to cast myScribe to a
		// ScribeImpl if using 1.4.1_01 or older.
		return ((ScribeImpl)myScribe).getParent(myTopic); 
		//return myScribe.getParent(myTopic); 
	}

	public NodeHandle[] getChildren() {
		return myScribe.getChildren(myTopic); 
	}




}
